#include <iostream>
#include <math.h>
#include <ArithmeticException.hpp>
#include <IndexOutOfBoundsException.hpp>
using namespace Exceptions;

#ifndef _CLASSMATHVECTOR_
#define _CLASSMATHVECTOR_

namespace Mathing {

/**
 * This class  represents a multi-dimensional general pourpose vector. It is a generic class
 * so it can be used with many data types. Although it can be used that way, class T should 
 * be in the numeric family to make sense of the arithmetic operators.
 * The dimension should not be lower than 1 since it would not makes sense.
 * Note: template<class T, unsigned int D=1> means:
 * class T: type of vector's elements
 * unsigned int D=1: vector's dimension and 1 is the default.
 * @author Marcelo R. Villarreal Fasanelli
 */
template<class T, unsigned int D=1>
class MathVector
{
public:
	/**
	 * Destructor: Releases the memory allocated by this object.
	 */
	~MathVector() {}

	/**
	 * Creates a new vector and sets its elements to zero.
	 */
	MathVector() {
		for (unsigned int i=0 ; i<D ; i++)
			this->vec[i] = 0;
	}

	/**
	 * Creates a new vector with values = (x, y). This constructor can be used
	 * when vector's dimension is lower than 3, other way, it will be thrown an
	 * IndexOutOfBoundsException.
	 * @param X The vector's x coordinate. (X, y)
	 * @param Y The vector's y coordinate. (x, Y)
	 */
	MathVector(T x, T y) {
		try {
			(*this)[0] = x;
			(*this)[1] = y;
		} catch (Exceptions::IndexOutOfBoundsException& e) {
			throw Exceptions::IndexOutOfBoundsException("Index out of bound in initialization of a two dimensional vector", e);
		}
	}

	/**
	 * Creates a new vector with values = (x, y, z). This constructor can be used
	 * when vector's dimension is lower than 4, other way, it will be thrown an
	 * IndexOutOfBoundsException.
	 * @param X The vector's x coordinate. (X, y, z)
	 * @param Y The vector's y coordinate. (x, Y, z)
	 * @param Y The vector's y coordinate. (x, y, Z)
	 */
	MathVector(T x, T y, T z) {
		try {
			(*this)[0] = x;
			(*this)[1] = y;
			(*this)[2] = z;
		} catch (Exceptions::IndexOutOfBoundsException& e) {
			Exceptions::IndexOutOfBoundsException ex("Index out of bound in initialization of a three dimensional vector", e);		
			throw ex;
		}
	}

	/**
	 * Creates a new vector with values = (x, y, z, w). This constructor can be used
	 * when vector's dimension is lower than 5, other way, it will be thrown an
	 * IndexOutOfBoundsException.
	 * @param X The vector's x coordinate. (X, y, z, w)
	 * @param Y The vector's y coordinate. (x, Y, z, w)
	 * @param Y The vector's y coordinate. (x, y, Z, w)
	 * @param Y The vector's y coordinate. (x, y, z, W)
	 */
	MathVector(T x, T y, T z, T w) {
		try {
			(*this)[0] = x;
			(*this)[1] = y;
			(*this)[2] = z;
			(*this)[3] = w;
		} catch (Exceptions::IndexOutOfBoundsException& e) {
			throw Exceptions::IndexOutOfBoundsException("Index out of bound in initialization of a four dimensional vector", e);
		}
	}

	/**
	 * Method to encapsulate the casting.
	 * @returns a pointer to the first element.
	 */
	T* getBuffer() {
		return (T*)this->vec;
	}

	/**
	 * This is to get the vector's data, as it is done with an array. It throws
	 * a IndexOutOfBoundsException if index is lower than 0 or greater than
	 * than the vector's dimension.
	 * @param index the element's index. (begins by the zero)
	 * @return The correspondent value.
	 */
	T& operator[](unsigned int index) {
		if (D <= index) {
			Exceptions::IndexOutOfBoundsException ex("Index out of bounds while geting an element.");
			throw ex;
		}
		return this->vec[index];
	}

	/**
	 * Function to get the vector's length.
	 * @return The vector's length.
	 */
	T length() {
		return (T)sqrt( (*this)%(*this) );
	}

	/**
	 * Function to set the vector's length mantaining its direction, this function
	 * throws an ArithmeticException if the actual length is zero.
	 * @param newLength The new vector's length.
	 * @return The same vector with the new length.
	 * @throws ArithmeticException When the actual length is zero.
	 */
	MathVector<T, D> setLength(T newLength) throw (ArithmeticException) {
		try {
			(*this) = this->normalized()*newLength;
		} catch (Exceptions::ArithmeticException& e) {
			throw Exceptions::ArithmeticException("The vector cannot be resized");
		}
		return (*this);
	}

	/**
	 * Function to get the vector with lentgh = 1, it throws an ArithmeticException
	 * if the actual length is zero.
	 * @return A vector with a length = 1.
	 * @throws ArithmeticException When the actual length is zero.
	 */
	MathVector<T, D> normalized() throw (ArithmeticException) {
		return (*this)/length();
	}

	/**
	 * Overload unary operator minus. It changes all vector's elements to its
	 * oposite sign
	 * @return A vector with the new values.
	 */
	MathVector<T, D> operator-() {
		MathVector<T, D> vec;
		for (unsigned int i=0 ; i<D ; i++)
			vec[i] = -this->vec[i];
		return vec;
	}

	/**
	 * Addition operator. This function does the addition between two vector
	 * returning a new one.
	 * @param vector the second vector.
	 * @return A vector with the new values.
	 */
	MathVector<T, D> operator+(MathVector<T, D> vector) {
		MathVector<T, D> vec;
		for (unsigned int i=0 ; i<D ; i++)
			vec[i] = this->vec[i] + vector[i];
		return vec;
	}

	/**
	 * Substraction operator. This function does the substraction between two vector
	 * returning a new one.
	 * @param vector the second vector.
	 * @return A vector with the new values.
	 */
	MathVector<T, D> operator-(MathVector<T, D> vector) {
		MathVector<T, D> vec;
		for (unsigned int i=0 ; i<D ; i++)
			vec[i] = this->vec[i] - vector[i];
		return vec;
	}

	/**
	 * Cross product operator. This method only makes sense when the vector's
	 * dimension is 3, if it is lower, then a IndexOutOfBoundsException is thrown.
	 * @param Vector The second vector.
	 * @return A normal vector.
	 */
	MathVector<T, D> operator*(MathVector<T, D> vector) {
		MathVector<T, D> vec;
		try {
			vec[0] = this->vec[1]*vector[2] - this->vec[2]*vector[1];
			vec[1] = this->vec[2]*vector[0] - this->vec[0]*vector[2];
			vec[2] = this->vec[0]*vector[1] - this->vec[1]*vector[0];
		} catch (Exceptions::IndexOutOfBoundsException& e) {
			throw Exceptions::IndexOutOfBoundsException("Index out of bounds while doing a cross product.");
		}
		return vec;
	}

	/**
	 * Dot Product operator. This function does the dot product between two
	 * vectors returning a new one.
	 * @param vector The second vector.
	 * @return A single value.
	 */
	T operator%(MathVector<T, D> vector) {
		T dot=0;
		for (unsigned int i=0 ; i<D ; i++)
			dot += this->vec[i] * vector[i];
		return dot;
	}

	/**
	 * Addition.
	 * @param Vector The second vector.
	 * @return This vector incremented.
	 */
	MathVector<T, D> operator+=(MathVector<T, D> vector) {
		return (*this) = (*this) + vector;
	}

	/**
	 * Substraction.
	 * @param Vector The second vector.
	 * @return This vector decremented.
	 */
	MathVector<T, D> operator-=(MathVector<T, D> vector) {
		return (*this) = (*this) - vector;
	}

	/**
	 * Cross product.
	 * @param Vector The second vector.
	 * @return This vector multiplicated?.
	 */
	MathVector<T, D> operator*=(MathVector<T, D> vector) {
		return (*this) = (*this) * vector;
	}

	/**
	 * Product operation with a single value, it afects the vector's length.
	 * @param cohef The value for doing the product.
	 * @return A vector with the new values.
	 */
	MathVector<T, D> operator*(T cohef) {
		MathVector<T, D> vec;
		for (unsigned int i=0 ; i<D ; i++)
			vec[i] = this->vec[i] * cohef;
		return vec;
	}

	/**
	 * Divition operation with a single value, it afects the vector's length, 
	 * the function throws an ArithmeticException if cohef = 0.
	 * @param Cohef A value != 0.
	 * @return A vector with the new values.
	 * @throws ArithmeticException If cohef = 0;
	 */
	MathVector<T, D> operator/(T cohef) throw (ArithmeticException) {
		//This is the only place on this class where the ArithmeticException is thrown.
		if (cohef == 0) {
			throw ArithmeticException("Error: A vector can't be divided by zero");
		}
		//If division can be done, returns it.
		MathVector<T, D> vec;
		for (unsigned int i=0 ; i<D ; i++)
			vec[i] = this->vec[i] / cohef;
		return vec;
	}

	/**
	 * Simple product.
	 * @param Cohef The value to multiplicate with.
	 * @return This vector multiplicated.
	 */	
	MathVector<T, D> operator*=(T cohef) {
		return (*this) = (*this) * cohef;
	}
    
	/**
	 * Divition.
	 * @param Cohef The value to divide with.
	 * @return This vector divided.
	 */
	MathVector<T, D> operator/=(T cohef) throw (ArithmeticException) {
		return (*this) = (*this) / cohef;
	}

	/**
	 * Logic operation, it compares all vector's elements.
	 * @param Vector The second vector.
	 * @return true if the elements of both vectors are equeals.
	 */
	bool operator==(MathVector<T, D> vector) {
		bool equals = true;
		for (unsigned int i=0 ; (i<D) && (equals) ; i++)
			equals &= (this->vec[i] == vector[i]);
		return equals;
	}

	/**
	 * Logic operation, it compares all vector's elements.
	 * @param Vector The second vector.
	 * @return true if the elements of both vectors aren't equeals.
	 */
	bool operator!=(const MathVector<T, D>& vector) {
		return !((*this) == vector);
	}

	void printToStdOut() {
		std::cout << "[ ";
		for (unsigned int d=0 ; d<D ; d++) {
			std::cout << this->vec[d];
			if (d<(D-1)) std::cout << ", ";
		}
		std::cout << " ]" << std::endl;
	}

protected:
	T vec[D];
};

/**
 * Simple operator to complement product conmutativity.
 */
template<class T, unsigned int D>
MathVector<T, D> operator*(T cohef, MathVector<T, D> vector) {
	return (vector * cohef);
}

typedef MathVector<char, 2>   Vector2c;
typedef MathVector<short, 2>  Vector2s;
typedef MathVector<int, 2>    Vector2i;
typedef MathVector<long, 2>   Vector2l;
typedef MathVector<float, 2>  Vector2f;
typedef MathVector<double, 2> Vector2d;
typedef MathVector<unsigned char, 2>  Vector32c;
typedef MathVector<unsigned short, 2> Vector32s;
typedef MathVector<unsigned int, 2>   Vector32i;
typedef MathVector<unsigned long, 2>  Vector32l;

typedef MathVector<char, 3>   Vector3c;
typedef MathVector<short, 3>  Vector3s;
typedef MathVector<int, 3>    Vector3i;
typedef MathVector<long, 3>   Vector3l;
typedef MathVector<float, 3>  Vector3f;
typedef MathVector<double, 3> Vector3d;
typedef MathVector<unsigned char, 3>  Vector3uc;
typedef MathVector<unsigned short, 3> Vector3us;
typedef MathVector<unsigned int, 3>   Vector3ui;
typedef MathVector<unsigned long, 3>  Vector3ul;

typedef MathVector<char, 4>   Vector4c;
typedef MathVector<short, 4>  Vector4s;
typedef MathVector<int, 4>    Vector4i;
typedef MathVector<long, 4>   Vector4l;
typedef MathVector<float, 4>  Vector4f;
typedef MathVector<double, 4> Vector4d;
typedef MathVector<unsigned char, 4>  Vector4uc;
typedef MathVector<unsigned short, 4> Vector4us;
typedef MathVector<unsigned int, 4>   Vector4ui;
typedef MathVector<unsigned long, 4>  Vector4ul;

}// namespace Mathing

#endif
